﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics.Contracts;

namespace ReactiveCode.Util
{
    static class ReflectionUtil
    {
        public static Action<object, object[], object> ToSetter(MemberInfo member)
        {
            Contract.Requires(member != null);
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    {
                        var field = (FieldInfo)member;
                        return (obj, args, value) => field.SetValue(obj, value);
                    }
                case MemberTypes.Property:
                    {
                        var property = (PropertyInfo)member;
                        if (!property.CanWrite)
                            return null;
                        return (obj, args, value) => property.SetValue(obj, value, args);
                    }

                case MemberTypes.Method:
                        {
                            var method = (member as MethodInfo);
                            if (method.GetParameters().Length == 1)
                                return (obj, args, value) => method.Invoke(obj, new[] { value });
                            else
                                return (obj, args, value) =>
                                {
                                    object[] allArgs = new object[args.Length + 1];
                                    Array.Copy(args, allArgs, args.Length);
                                    allArgs[allArgs.Length - 1] = value;
                                    method.Invoke(obj, allArgs);
                                };
                        }

                default:
                    throw new ArgumentException("member");
            }
        }
        public static Func<object, object[], object> ToGetter(MemberInfo member)
        {
            Contract.Requires(member != null);
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    {
                        var field = (FieldInfo)member;
                        return (obj, args) => field.GetValue(obj);
                    }

                case MemberTypes.Property:
                    return (member as PropertyInfo).GetValue;

                case MemberTypes.Method:
                    return (member as MethodInfo).Invoke;

                default:
                    throw new ArgumentException("member");
            }
        }
        public static MemberInfo Normalize(this MemberInfo member)
        {
            Contract.Requires(member != null);
            const string propAccessorPrefix = "get_";
            if (member.MemberType == MemberTypes.Method && member.DeclaringType != null)
            {
                MethodInfo method = (MethodInfo)member;
                if (method.IsSpecialName)
                {
                    BindingFlags flags = BindingFlags.DeclaredOnly;
                    flags |= method.IsStatic ? BindingFlags.Static : BindingFlags.Instance;
                    flags |= method.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic;

                    PropertyInfo prop;
                    if (member.Name.StartsWith(propAccessorPrefix))
                    {
                        Type[] indexerArgTypes = Type.EmptyTypes;
                        ParameterInfo[] parameters = method.GetParameters();
                        if (parameters.Length > 0)
                            indexerArgTypes = parameters.Select(p => p.ParameterType).ToArray();

                        prop = member.DeclaringType.GetProperty(member.Name.Substring(propAccessorPrefix.Length), flags, null,
                                                                method.ReturnType, indexerArgTypes, null);
                        if (prop != null && prop.GetGetMethod(true) == method)
                            return prop;
                    }


                    prop = member.DeclaringType.GetProperties(flags).FirstOrDefault(p => p.GetGetMethod(true) == method);
                    if (prop != null)
                        return prop;
                }
            }

            return member;
        }
        //[Pure]
        //public static Type GetEventArgsType(Type eventHandlerDelegate)
        //{
        //    Contract.Requires(eventHandlerDelegate != null);
        //    if (eventHandlerDelegate == typeof(EventHandler))
        //        return typeof(EventArgs);
        //    else if (eventHandlerDelegate.IsGenericType && eventHandlerDelegate.GetGenericTypeDefinition() == typeof(EventHandler<>))
        //        return eventHandlerDelegate.GetGenericArguments().Single();
        //    else
        //    {
        //        var parms = GetParameters(eventHandlerDelegate);
        //        if (parms.Length == 2 && parms[0].ParameterType == typeof(object) && 
        //            (typeof(EventArgs).IsAssignableFrom(parms[1].ParameterType) || parms[1].ParameterType.Name.Contains("EventArgs")))
        //            return parms[1].ParameterType;
        //        return null;
        //    }
        //}
        public static TDelegate ToDelegate<TDelegate>(this MethodInfo method)
            where TDelegate : class
        {
            Contract.Requires(method != null);
            Contract.Requires(typeof(TDelegate).IsSubclassOf(typeof(Delegate)));

            return Delegate.CreateDelegate(typeof(TDelegate), method) as TDelegate;
        }

        public static object DefaultValue(this Type type)
        {
            Contract.Requires(type != null);

            if (!type.IsValueType)
                return null;

            switch (Type.GetTypeCode(type))
            {
                case TypeCode.Boolean:
                case TypeCode.Byte:
                case TypeCode.Char:
                case TypeCode.Decimal:
                case TypeCode.Double:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.SByte:
                case TypeCode.Single:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                    return Convert.ChangeType(0, type);

                case TypeCode.DateTime:
                    return new DateTime();

                case TypeCode.Object:
                    return Activator.CreateInstance(type);
            }
            return null;
        }

        [Pure]
        public static bool IsStatic(this MemberInfo member)
        {
            Contract.Requires(member != null);
            switch (member.MemberType)
            {
                case MemberTypes.Event:
                    var e = member as EventInfo;
                    return IsStatic(e.GetAddMethod() ?? e.GetRemoveMethod() ?? e.GetRaiseMethod() ?? e.GetOtherMethods().FirstOrDefault());

                case MemberTypes.Field:
                    return (member as FieldInfo).IsStatic;
                    
                case MemberTypes.Property:
                    var prop = member as PropertyInfo;
                    Contract.Assume(prop != null);
                    member = prop.GetGetMethod() ?? prop.GetSetMethod();
                    return member != null && member.IsStatic();

                case MemberTypes.NestedType:
                case MemberTypes.TypeInfo:
                    var type = member as Type;
                    return type.IsAbstract && type.IsSealed;

                case MemberTypes.Method:
                case MemberTypes.Constructor:
                    return (member as MethodInfo).IsStatic;

                default:
                    throw new ArgumentException("member");
            }
        }
        public static bool IsIndexer(this PropertyInfo property)
        {
            Contract.Requires(property != null);
            var method = property.GetGetMethod();
            if (method != null)
                return method.GetParameters().Length > 0;

            method = property.GetSetMethod();
            return method != null && method.GetParameters().Length > 1;
        }
        public static ParameterInfo[] GetParameters(this Type delegateType)
        {
            Contract.Requires(delegateType != null && delegateType.IsSubclassOf(typeof(Delegate)));
            var invoke = delegateType.GetMethod("Invoke");
            Contract.Assume(invoke != null);
            return invoke.GetParameters();
        }

        internal static Type GetValueType(MemberInfo member)
        {
            Contract.Requires(member != null);
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    return ((FieldInfo)member).FieldType;

                case MemberTypes.Method:
                    return ((MethodInfo)member).ReturnType;

                case MemberTypes.Property:
                    return ((PropertyInfo)member).PropertyType;

                default:
                    return null;
            }
        }
    }
}
